/*
 * Created by 2019年4月17日
 */
package top.v5it.japi.ddd.core.dao.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.cursor.Cursor;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import top.v5it.japi.ddd.core.dao.BasicDao;
import top.v5it.japi.ddd.core.dao.impl.mybatis.GenericDao;
import top.v5it.japi.ddd.core.entity.Entity;
import top.v5it.japi.ddd.core.exception.DaoException;

import java.io.Serializable;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * The implement of BasicDao with Jdbc.
 *
 * @author fangang
 */
@RequiredArgsConstructor
public class BasicDaoJdbcImpl implements BasicDao {
    private final GenericDao dao;
    private final NamedParameterJdbcTemplate jdbcTemplate;

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> void insert(T entity) {
        if (entity == null) throw new DaoException("The entity is null");
        DaoHelper helper = EntityHelper.readDataFromEntity(entity);
        try {
            dao.insert(helper.getTableName(), helper.getColumns(), helper.getValues());
        } catch (DataAccessException e) {
            throw new DaoException("Error when inserting entity", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> void update(T entity) {
        if (entity == null) throw new DaoException("The entity is null");
        DaoHelper helper = EntityHelper.readDataFromEntity(entity);
        try {
            dao.update(helper.getTableName(), helper.getColMap(), helper.getPkMap());
        } catch (DataAccessException e) {
            throw new DaoException("Error when updating entity", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> void insertOrUpdate(T entity) {
        if (entity == null) throw new DaoException("The entity is null");
        DaoHelper helper = EntityHelper.readDataFromEntity(entity);
        try {
            dao.insert(helper.getTableName(), helper.getColumns(), helper.getValues());
        } catch (DataAccessException e) {
            if (e.getCause() instanceof SQLIntegrityConstraintViolationException)
                update(entity);
            else throw new DaoException("Error when inserting entity", e);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T, S extends Collection<T>> void insertOrUpdateForList(S list) {
        for (Object entity : list) insertOrUpdate(entity);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> void delete(T entity) {
        DaoHelper helper = EntityHelper.readDataFromEntity(entity);
        dao.delete(helper.getTableName(), helper.getPkMap());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T, S extends Collection<T>> void deleteForList(S list) {
        for (Object entity : list) delete(entity);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> void deleteForList(Collection<S> ids, T template) {
        DaoHelper helper = EntityHelper.prepareForList(ids, template);
        dao.deleteForList(helper.getTableName(), helper.getPkMap());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> T load(S id, T template) {
        if (id == null || template == null) throw new DaoException("Illegal parameters!");
        template.setId(id);
        DaoHelper helper = EntityHelper.readDataFromEntity(template);
        List<Map<String, Object>> list = dao.find(helper.getTableName(), helper.getPkMap());
        if (list.isEmpty()) return null;
        Map<String, Object> map = list.get(0);
        return EntityHelper.convertMapToEntity(map, template);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> List<T> loadForList(Collection<S> ids, T template) {
        DaoHelper helper = EntityHelper.prepareForList(ids, template);
        List<Map<String, Object>> list = dao.load(helper.getTableName(), helper.getPkMap());

        // 转换结果集，从List<Map>转换为List<Entity>
        List<T> listOfEntity = new ArrayList<>();
        for (Map<String, Object> map : list) {
            @SuppressWarnings("unchecked")
            T temp = (T) template.clone();
            T entity = EntityHelper.convertMapToEntity(map, temp);
            listOfEntity.add(entity);
        }
        return listOfEntity;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> List<T> loadAll(T template) {
        DaoHelper helper = EntityHelper.readDataFromEntity(template);
        List<Map<String, Object>> list = dao.find(helper.getTableName(), helper.getColMap());

        List<T> listOfEntity = new ArrayList<>();
        for (Map<String, Object> map : list) {
            @SuppressWarnings("unchecked")
            T temp = (T) template.clone();
            T entity = EntityHelper.convertMapToEntity(map, temp);
            listOfEntity.add(entity);
        }
        return listOfEntity;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> void delete(S id, T template) {
        if (id == null || template == null) throw new DaoException("Illegal parameters!");
        T entity = this.load(id, template);
        this.delete(entity);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> List<T> cursorAll(T template, Predicate<T> test) {
        DaoHelper helper = EntityHelper.readDataFromEntity(template);
        try (final Cursor<Map<String, Object>> list = dao.cursor(helper.getTableName(), helper.getColMap())) {
            final List<T> listOfEntity = new ArrayList<>();
            list.forEach(t -> {
                @SuppressWarnings("unchecked")
                T temp = (T) template.clone();
                T entity = EntityHelper.convertMapToEntity(t, temp);
                if (test.negate().test(entity)) return;
                listOfEntity.add(entity);
            });
            return listOfEntity;
        } catch (Exception e) {
            throw new DaoException("Error when querying entity!");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> Cursor<Map<String, Object>> cursorAll(T template) {
        DaoHelper helper = EntityHelper.readDataFromEntity(template);
        return dao.cursor(helper.getTableName(), helper.getColMap());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> List<T> loadAllByCondition(Wrapper<T> wrapper) {
        final T template = wrapper.getEntity();
        DaoHelper helper = EntityHelper.readDataFromEntity(template);
        List<Map<String, Object>> list = dao.findByCondition(helper.getTableName(), wrapper.getConditions(), wrapper.getSegments());

        final List<T> listOfEntity = new ArrayList<>();
        for (Map<String, Object> map : list) {
            @SuppressWarnings("unchecked")
            T temp = (T) template.clone();
            T entity = EntityHelper.convertMapToEntity(map, temp);
            listOfEntity.add(entity);
        }
        return listOfEntity;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> List<Map<String, Object>> loadAllByCombination(Wrapper<T> wrapper) {
        final T template = wrapper.getEntity();
        DaoHelper helper = EntityHelper.readDataFromEntity(template);
        return dao.loadAllByCombination(wrapper.getColumnSegments(), helper.getTableName(), wrapper.getConditions(), wrapper.getSegments());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public <T> List<T> execute(Wrapper<T> wrapper) {
        final T template = wrapper.getEntity();
        final List<Map<String, Object>> list = dao.execute(wrapper.getSql(), wrapper.getValue());
        if (!Entity.class.isAssignableFrom(template.getClass())) return (List<T>) list;
        final List<T> listOfEntity = new ArrayList<>();
        Entity<?> src = (Entity<?>) template;
        for (Map<String, Object> map : list) {
            T temp = (T) src.clone();
            T entity = EntityHelper.convertMapToEntity(map, temp);
            listOfEntity.add(entity);
        }
        return listOfEntity;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T> Optional<T> one(Wrapper<T> wrapper) {
        final List<T> list = execute(wrapper);
        return Optional.ofNullable(CollUtil.getFirst(list));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <S extends Serializable, T extends Entity<S>> Cursor<Map<String, Object>> cursorAll(Wrapper<T> wrapper) {
        return dao.executeCursor(wrapper.getSql(), wrapper.getValue());
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public <T, S extends Collection<T>> void jdbcInsertForList(S list) {
        if (list == null) throw new DaoException("The list is null");
        final String template = "INSERT INTO {}{} VALUES{}";
        final T entity = list.iterator().next();
        final DaoHelper helper = EntityHelper.readDataFromEntity(entity);
        final List<Object> columns = helper.getColumns();
        final String column = columns.stream().map(Objects::toString).collect(Collectors.joining(", ", "(", ")"));
        final String value = columns.stream().map(t -> ":" + t).collect(Collectors.joining(", ", "(", ")"));
        final String sql = StrUtil.format(template, helper.getTableName(), column, value);
        final BeanPropertySqlParameterSource[] batchArgs = list.stream()
                .map(BeanPropertySqlParameterSource::new).toArray(BeanPropertySqlParameterSource[]::new);
        jdbcTemplate.batchUpdate(sql, batchArgs);
    }
}
